package com.sychip.fhc.app.drawer.screen


import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.viewModelScope
import com.sychip.fhc.MainApplication
import com.sychip.fhc.app.data.repo.BleDeviceRepo
import com.sychip.fhc.app.data.source.dto.BleDeviceDto
import com.sychip.fhc.base.BaseAppViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onEmpty
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeout
import no.nordicsemi.kotlin.ble.client.android.CentralManager
import no.nordicsemi.kotlin.ble.client.android.Peripheral
import no.nordicsemi.kotlin.ble.core.Phy
import timber.log.Timber
import java.util.UUID
import kotlin.time.Duration.Companion.seconds
import kotlin.uuid.ExperimentalUuidApi


/**
 * ViewModel for the PullList screen.
 */
@HiltViewModel
class BleScanViewModel @Inject constructor(
    app : MainApplication,
    private val bleRepo: BleDeviceRepo,
    private val centralManager: CentralManager,
) : BaseAppViewModel(app) {

    sealed interface UiTestState {
        object Null : UiTestState
        object Loading : UiTestState
        data class Success(val data: List<BleDeviceDto>) : UiTestState
        data class Error(val throwable: Throwable? = null) : UiTestState

    }

    private val _testFlow = MutableStateFlow<UiTestState>(UiTestState.Null)
    private val _testData : MutableList<BleDeviceDto>  =  mutableStateListOf<BleDeviceDto>()
    val testFlow: StateFlow<UiTestState> = _testFlow.asStateFlow()




    private val _bleDevices: MutableStateFlow<MutableList<BleDeviceDto>> = MutableStateFlow(mutableListOf())
    val bleDevices = _bleDevices.asStateFlow()
    private var _blePeripherals:List<Peripheral> = emptyList()

    fun setScannedPeripheral(lst:List<Peripheral>) {
        _blePeripherals = lst
        _bleDevices.update { mutableListOf() }
        val  tmpList :MutableList<BleDeviceDto> = mutableListOf()
        viewModelScope.launch {
            lst.forEach { it->
                val dto = BleDeviceDto(name = it.name?:"", devId = it.identifier, mac = it.address, isCompleted = false)

                tmpList.add(dto)
            }
            _bleDevices.update { tmpList }
        }
    }

     fun reloadTestData() {
        Timber.i("    >>>>>>>>>>>>>>>>>>>>>>>>>   reloadTestData  reloadTestData   >>>>>>>>>>>>>>>>>>>>>>>>> ")
        _testData.clear()
        _testFlow.update { UiTestState.Loading }
        loadTestData()
    }

     fun loadTestData() {
         // 还有cancel ensureActive，cancelable等写法比较实用
         bleRepo.getAllBleDevices().map { result ->
             //map不是必须，我们可以在里面进行转换数据，然后 onEach....，如果不需要转换，可以移除map结构
             //UiTestState.Success(result) as UiTestState
             result
         }.onCompletion { cause ->

         }.onEach { result ->
             delay(3000)
             _testData.addAll(result)
             _testFlow.update { UiTestState.Success( data = _testData ) }
         }.catch { error ->
             UiTestState.Error(error)
         }.onStart {
             UiTestState.Loading
         }.launchIn(viewModelScope)
    }

    fun addData() {
        bleRepo.createBleDevice(name="ADD"+_testData.size, mac = UUID.randomUUID().toString()).onEach { result ->
            Timber.i("   addData  222 >>>>>>>>>>>>>>>>>>>>>>>>> %s", result.mac)
            _testData.add(result)
            Timber.i("   addData  33333 >>>>>>>>>>>>>>>>>>>>>>>>> %s", _testData.size)

            _testFlow.update { UiTestState.Success( data = _testData ) }
        }.catch { error ->
            UiTestState.Error(error)
        }.onStart {

        }.launchIn(viewModelScope)
    }

    fun deleteData(index:Int = -1, devId: String) {
        val delItem =  if(index < 0)   _testData.find { it.devId == devId } else _testData[index]
        Timber.i("   addData  111 >>>>>>>>>>>>>>>>>>>>>>>>> %s", index)
        viewModelScope.launch{
            val rlt = bleRepo.deleteBleDevice(devId)
            if(rlt > 0){
                _testData.remove(delItem)
            }
        }
    }


      fun connect(ble: BleDeviceDto, autoConnect: Boolean) {
         val peripheral = _blePeripherals.find { it.identifier == ble.devId }
         if(peripheral == null) return
         CoroutineScope(context = Dispatchers.IO)
             .apply {
                 launch {
                     withTimeout(5000) {
                         try {
                             centralManager.connect(
                                 peripheral = peripheral,
                                 options = if (autoConnect) {
                                     CentralManager.ConnectionOptions.AutoConnect
                                 } else {
                                     CentralManager.ConnectionOptions.Direct(
                                         timeout = 3.seconds,
                                         retry = 2,
                                         retryDelay = 1.seconds,
                                         Phy.PHY_LE_2M,
                                     )
                                 },
                             )
                             val dto = _bleDevices.value.find { it.devId == peripheral.identifier }
                             dto?.rssi = peripheral.readRssi()
                             _bleDevices.update { _bleDevices.value }
                             Timber.i("Connected to ${peripheral.name}!")
                         }catch (e: Exception) {
                             Timber.e(e, "Connection attempt failed")
                         }
                     }
                     observerPhy(peripheral, this)
                     observeConnectionParameters(peripheral, this)
                     observerServices(peripheral, this)
                 }}

    }


    private fun observerPhy(peripheral: Peripheral, scope: CoroutineScope) {
        peripheral.phy
            .onEach {
                Timber.i("PHY changed to: $it")
            }
            .onEmpty {
                Timber.w("PHY didn't change")
            }
            .onCompletion {
                Timber.d("PHY collection completed")
            }
            .launchIn(scope)
    }

    private fun observeConnectionParameters(peripheral: Peripheral, scope: CoroutineScope) {
        peripheral.connectionParameters
            .onEach {
                Timber.i("Connection parameters changed to: $it")
            }
            .onEmpty {
                Timber.w("Connection parameters didn't change")
            }
            .onCompletion {
                Timber.d("Connection parameters collection completed")
            }
            .launchIn(scope)
    }

    @OptIn(ExperimentalStdlibApi::class, ExperimentalUuidApi::class)
    private fun observerServices(peripheral: Peripheral, scope: CoroutineScope) {
        peripheral.services()
            .onEach {
                Timber.i("Services changed: $it")

                // Read values of all characteristics.
                it.forEach { remoteService ->
                    remoteService.characteristics.forEach { remoteCharacteristic ->
                        try {
                            val value = remoteCharacteristic.read()
                            Timber.i("Value of ${remoteCharacteristic.uuid}: 0x${value.toHexString()}")
                        } catch (e: Exception) {
                            Timber.e("Failed to read ${remoteCharacteristic.uuid}: ${e.message}")
                        }
                    }
                }

                it.forEach { remoteService ->
                    remoteService.characteristics.forEach { remoteCharacteristic ->
                        try {
                            Timber.w("Subscribing to ${remoteCharacteristic.uuid}...")
                            remoteCharacteristic.subscribe()
                                .onEach { newValue ->
                                    Timber.i("Value of ${remoteCharacteristic.uuid} changed: 0x${newValue.toHexString()}")
                                }
                                .onEmpty {
                                    Timber.w("No updates from ${remoteCharacteristic.uuid}")
                                }
                                .onCompletion {
                                    Timber.d("Stopped observing updates from ${remoteCharacteristic.uuid}")
                                }
                                .launchIn(scope)
                            Timber.i("Notifications for ${remoteCharacteristic.uuid} are now ${if (remoteCharacteristic.isNotifying) "enabled" else "disabled"}")
                        } catch (e: Exception) {
                            Timber.e("Failed to subscribe to ${remoteCharacteristic.uuid}: ${e.message}")
                        }
                    }
                }
            }
            .onEmpty {
                Timber.w("No services found")
            }
            .onCompletion {
                Timber.d("Service collection completed")
            }
            .launchIn(scope)
    }
}


